package com.xpec.cipher.h5handler;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import com.xpec.cipher.CipherConnectionConstant;
import com.xpec.cipher.CipherConnectionStatus;
import com.xpec.cipher.CipherHandler;
import com.xpec.cipher.handler.CipherConnectionPacketHandler;
import com.xpec.cipher.tals.PseudoRandom;
import com.xpec.cipher.tals.TalsConstance;
import com.xpec.cipher.tals.record.ChangeCipherSpecRecord;
import com.xpec.cipher.tals.record.RSAEncryptedPreMatserRecord;

public class H5ClientChangeCipherHandler implements CipherConnectionPacketHandler
{
    @Override
	public void receiveHandle(CipherHandler cipherHandler)
	{
		
		// 接收到的是server发过来的HandShake包
		int contentType = cipherHandler.readUnsignedByteFromRealContent();
		if (contentType != TalsConstance.CONTENT_TYPE_HAND_SHAKE){
			return;
		}
		/// server 在 handshake 階段還有第二包, public key !
		int handShakeType = cipherHandler.readUnsignedByteFromRealContent();
		if (handShakeType != TalsConstance.HAND_SHAKE_TYPE_SERVER_RSA_PUBLIC_KEY){
			return;
		}
		
		
		/**
		Server 送過來的 Key 結構如下 :
		struct {
			BYTE					RSA_KEY_MODULUS[256];	
			UINT					RSA_PUBLIC_KEY_EXP;		//必须为大于32767的奇数，一般可直接取32767
			HMAC_Algorithm			hmac_algo;				//使用的HMAC方法 (MD5 or SHA-1)
			BYTE  					HMAC_hash_size_in_use;  //传送的MAC尺寸
			Data_Encrypt_Algorithm	symetric_algo; 			//使用的对称加密方法 (777, ARC4, DES)
		}ServerRSAPubKBody;
		 */
		
		/// 這裡有個嚴重問題 ... Server那邊取出來的 public key 其實有 257 個 bytes
		/// 但是只取後 256 bytes 傳給 client, 觀察到 index 0 的那個 byte 永遠是 0
		/// 因此這裡把這個現象還原, 此 public key 才能用
		byte[] rsaModulus = new byte[TalsConstance.RSA_MODULUS_SIZE + 1];
		byte[] rsaModulusTemp = new byte[TalsConstance.RSA_MODULUS_SIZE];
		cipherHandler.readByteFromRealContent(rsaModulusTemp);
		rsaModulus[0] = (byte)0;
		for(int i=0; i<TalsConstance.RSA_MODULUS_SIZE; i++){
			rsaModulus[i+1] = rsaModulusTemp[i];
		}
		
		byte[] rsaExp = new byte[TalsConstance.RSA_EXP_SIZE];
		cipherHandler.readByteFromRealContent(rsaExp);
		
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_HMAC_ALGO,
				(byte)cipherHandler.readUnsignedByteFromRealContent());
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_MAC_SIZE, 
				(byte)cipherHandler.readUnsignedByteFromRealContent());
		byte algoType = (byte)cipherHandler.readUnsignedByteFromRealContent();
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_CIPHER_ALGO, 
				algoType);
		

		/**
		 * 生出 server 的 public key
		 */
		RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(rsaModulus), new BigInteger(rsaExp) );
		KeyFactory keyFac = null;
		try {
			keyFac = KeyFactory.getInstance("RSA");
		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		}
		RSAPublicKey publicKey = null;
		try {
			publicKey = (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
		} catch (InvalidKeySpecException e1) {
			e1.printStackTrace();
		}


		/**
		 * 生成 秘密前体 (pre master secret)
		 * 通讯握手阶段，客户端随机生成的46 Bytes随机内容，通过RSA Public加密后发给服务器。
		 */
		Random random = new Random(System.currentTimeMillis());
		byte[] preMasterSecret = new byte[ TalsConstance.PRE_SECRET_RANDOM_BYTES_SIZE ];
		random.nextBytes(preMasterSecret);
		byte[] ePreSecret = null; /// 加密過後的秘密前体

		try {
			Cipher cipher = Cipher.getInstance("RSA/ECB/NOPadding");
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			ePreSecret = cipher.doFinal( preMasterSecret );
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		
		
		/** 計算 master secret
		 * 使用“秘密前体”作为伪随机发生器的key，发生48 Bytes内容，即为“主要秘密”/Master Secret。
		 * Master Secret [48] = PRG(Pre-Master Secret,Client Random XOR Server Random)。
		 * 取 Master Secret[0...15] 共 16Bytes 为 md5_mac_secret[16]，用于生成加密摘要。
		 * 取 Master Secret[32...47] 共 16Bytes 为 symmetric_key[16]，用于ARC4、DES、777加密。
		 */
		byte[] clientRandom = (byte[])cipherHandler.getParameter(CipherConnectionConstant.PARA_KEY_CLIENT_RANDOM);
		byte[] serverRandom = (byte[])cipherHandler.getParameter(CipherConnectionConstant.PARA_KEY_SERVER_RANDOM);
		byte[] masterSecret = PseudoRandom.generatePRG(
				preMasterSecret, clientRandom, serverRandom);

		byte[] macKey = Arrays.copyOfRange(masterSecret, 0, 16);
		byte[] symmetricKey = Arrays.copyOfRange(masterSecret, 32, 48);
		
		/// 存下來, 之後會用到
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_MASTER_KEY, masterSecret);
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_MAC_KEY, macKey);
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_KEY, symmetricKey);
		
		Object algo = H5ServerPreMasterHandler.genCipherAlgo(cipherHandler, symmetricKey, algoType);
		if (null != algo)
			cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_SERVER_CIPHER_OBJECT, algo);
		
		
		/// 給後台送出用 publicKey加密過的秘密前体
		/**
		 RSAEncryptedPreMatser对应的结构如下：
			struct {
				UINT					CIPHERTEXT_LENGTH;
				RSA_ENCRYPTED_BYTE		RSAClientPreMaster[CIPHERTEXT_LENGTH];
			}RSAEncryptedPreMatser;
		 */
		RSAEncryptedPreMatserRecord record = new RSAEncryptedPreMatserRecord(ePreSecret);
		cipherHandler.sendData(record.resolve(), false); /// 送出給 Server !
		

		/**
		 * 傳送 Change Cipher Spec
		 * 這裡應該是要用 Server 送過來的方式加密, 但是 Flex 上目前是寫死的, 使用 777, Server 收到也沒檢查
		 * 應該要看 cipherHandler.getParameter(CipherConnectionConstant.PARA_KEY_CIPHER_TYPE) 
		 */
		byte[] cipherData = PseudoRandom.generatePRG(masterSecret, clientRandom, serverRandom);
		byte[] changeCipherSpec = cipherData;
		ChangeCipherSpecRecord cRecord = new ChangeCipherSpecRecord(changeCipherSpec);
		cipherHandler.sendData(cRecord.resolve(), false);
		
		/**
		 * 改變狀態, 開始等待 Server的 Cipher信息
		 */
		cipherHandler.setParameter(CipherConnectionConstant.PARA_KEY_STATUS,
				CipherConnectionStatus.CLIENT_RECEIVE_SERVER_CIPHER);
	}

	@Override
	public void sendHandle(CipherHandler cipherHandler) {
		cipherHandler.passMessage(CipherHandler.PASS_MESSAGE_SEND);
	}

}
